﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace MVCHelperExtensions
{
  /// <summary>
  /// Static class containing utility methods for HTMLHelper and UrlHelper extensions
  /// </summary>
  public static class MVCHelperUtilities
  {
    /// <summary>
    /// Retrieves the specified HTMLHelperAttribute from the specified modelType
    /// </summary>
    /// <typeparam name="TAttribute"></typeparam>
    /// <param name="modelType"></param>
    /// <returns>the attribute, if any, or default value</returns>
    public static TClassAttribute GetClassAttribute<TClassAttribute>(Type modelType)
    {
      TClassAttribute[] attributeCollection =
        modelType.GetCustomAttributes(typeof(TClassAttribute), false) as TClassAttribute[];
      TClassAttribute attribute = attributeCollection.FirstOrDefault();
      return attribute;
    }

    /// <summary>
    /// Gets the ID of the specified model instance.
    /// </summary>
    /// <typeparam name="TModel"></typeparam>
    /// <param name="modelInstance"></param>
    /// <returns>the model ID</returns>
    /// <example><code>
    /// MyModel myModel = new MyModel();
    /// int myModelID = GetModelID(myModel);
    /// </code></example>
    public static int GetModelID<TModel>(TModel modelInstance)
    {
      string identifierName = null;
      Type modelType = typeof(TModel);
      int modelID;

      // Determine the model instance's id
      // Get a property with an identifier attribute, if any
      PropertyInfo identifyingPropertyInfo = modelType
        .GetProperties()
        .FirstOrDefault(propInfo =>
        {
          return
            propInfo.GetCustomAttributes(typeof(MVCHelperIdentifierAttribute), false).Length > 0
            && propInfo.PropertyType == typeof(int); // Only select the property if it is an int.
        }
        );
      if (identifyingPropertyInfo != null)
      {
        // A property with an identifying attribute was found
        identifierName = identifyingPropertyInfo.Name;
      }
      else
      {
        // There was no property with an identifier attribute found, so look for a property with a name matching the list of defaults
        identifyingPropertyInfo = modelType
          .GetProperties()
          .FirstOrDefault(propInfo =>
          {
            return
              MVCHelpersDefaultValues.DEFAULT_IDENTIFYING_PROPERTY_NAMES
              .FirstOrDefault(defaultName => defaultName == propInfo.Name) != null
              && propInfo.PropertyType == typeof(int);
          }
        );
        if (identifyingPropertyInfo != null)
        {
          // There was a property found with default name
          identifierName = identifyingPropertyInfo.Name;
        }
        else
        {
          // There was no property with a default id name or tagged with an attribute, so it is
          // impossible to determine what the model's id is.
          throw new NonInferrableModelIdException(
            String.Format("Could not find identifying property for {0}", modelType.Name)
          );
        }
      }

      modelID = (int)modelType.GetProperty(identifierName).GetValue(modelInstance, null);
      return modelID;
    }

    /// <summary>
    /// Returns the name of the controller for the specified type by
    /// checking that type's class attributes or by using a default
    /// (conventional) value.
    /// </summary>
    /// <param name="modelType"></param>
    /// <returns></returns>
    public static string GetControllerName(Type modelType)
    {
      // Find Controller name
      string controllerName = null;

      MVCHelperControllerNameAttribute controllerNameAttribute =
        MVCHelperUtilities.GetClassAttribute<MVCHelperControllerNameAttribute>(modelType);
      if (controllerNameAttribute != null)
      {
        controllerName = controllerNameAttribute.ControllerName;
      }
      else
      {
        controllerName = Inflector.Net.Inflector.Pluralize(modelType.Name);
        // TODO: Implement default controller name based on pluralization of the model name
        //       I'm currently looking into existing libraries for this purpose.
      }
      return controllerName;
    }

    /// <summary>
    /// Uses reflection to create a dictionary with of the objects properties
    /// </summary>
    /// <param name="o"></param>
    /// <returns></returns>
    public static Dictionary<string, object>ObjectToDictionary(object o)
    {
      Dictionary<string, object> dictionary = new Dictionary<string, object>();
      if (o == null)
        return dictionary;

      Type oType = o.GetType();
      var properties = oType.GetProperties();
      foreach (var property in properties)
      {
        dictionary.Add(property.Name, property.GetValue(o, null));
      }

      return dictionary;
    }
  }
}
